#include "Socket.h"

/**
 * 创建一个服务端socket
 * @param env env
 * @param obj obj
 * @return 创建成功返回socket的descriptor；否则返回-1
 */
static int NewTcpSocket(JNIEnv *env,jobject obj){
    LogMessage(env,obj,"Constructing a New Tcp Connect ...");
    int socketDescriptor = socket(PF_INET,SOCK_STREAM,0);

    if(-1 == socketDescriptor){
        ThrowErrorNoException(env,"java/io/IOException",errno);
    }
    LogMessage(env,obj,"Constructing a New Tcp result:%d",socketDescriptor);
    return socketDescriptor;
}

/**
 * 绑定Socket的端口号
 * @param env env
 * @param obj obj
 * @param socketDescriptor 要绑定的Socket的标识符
 * @param port 要绑定的端口号
 */
static void BindSocketToPort(JNIEnv * env,jobject obj,
                            int socketDescriptor, unsigned short port){
    LogMessage(env,obj,"Bind Socket To Port Start");
    struct sockaddr_in address;

    memset(&address,0, sizeof(address));

    address.sin_family = PF_INET;
    address.sin_addr.s_addr = htonl(INADDR_ANY);        // 绑定到所有地址
    address.sin_port = htons(port);     // 将端口号转换为网络字节顺序

    LogMessage(env,obj,"bind to port:%d.",port);
    if(-1 == bind(socketDescriptor,(struct sockaddr*)&address, sizeof(address))){
        ThrowErrorNoException(env,"java/io/IOException",errno);
    }
}

/**
 * 得到绑定到指定socket描述符的端口号
 * @param env env
 * @param obj obj
 * @param socketDescriptor socket描述符
 * @return 如果返回-1，则绑定失败；否则返回绑定到的端口号
 */
static unsigned short GetSocketPort(JNIEnv *env,jobject obj,int socketDescriptor){
    unsigned short port = 0;
    struct sockaddr_in address;
    socklen_t length = sizeof(address);

    if(-1 == getsockname(socketDescriptor,(struct sockaddr*)&address, &length)){
        ThrowErrorNoException(env,"java/io/IOException",errno);
    }else{
        port = ntohs(address.sin_port);
        LogMessage(env,obj,"Binded to random prot:%d.",port);
    }
    return port;
}

/**
 * 监听连接
 * @param env env
 * @param obj obj
 * @param socketDescriptor socket描述符
 * @param backlog 允许的最大的连接数
 */
static void ListenSocket(JNIEnv* env,jobject obj,int socketDescriptor,int backlog){
    LogMessage(env,obj,"Allow Max Connecting is %d",backlog);
    if(-1 == listen(socketDescriptor,backlog)){
        ThrowErrorNoException(env,"java/lang/IOException",errno);
    }
}
/**
 * 打印ip地址
 * @param env env
 * @param obj obj
 * @param message message
 * @param address IP地址
 */
static void LogAddress(JNIEnv *env,jobject obj,const char *message,const struct sockaddr_in* address){
    char ip[INET_ADDRSTRLEN];

    // 将ip地址转换为字符串
    if(NULL == inet_ntop(PF_INET,&(address->sin_addr),ip,INET_ADDRSTRLEN)){
        ThrowErrorNoException(env,"java/lang/IOException",errno);
    }else{
        // 将端口号转换为主机字节顺序
        unsigned short port = ntohs(address->sin_port);
        LogMessage(env,obj,"%s %s:%u",message,ip,port);
    }
}

/**
 * 指定Socket阻塞和等待进来的客户端连接
 * @param env env
 * @param obj obj
 * @param socketDescriptor 服务端Socket
 * @return 连接进来的客户端socket描述符或者-1
 */
static int AcceptOnSocket(JNIEnv *env,jobject obj,int socketDescriptor){
    struct sockaddr_in address;
    socklen_t length = sizeof(address);
    int clientSocketDescriptor = accept(socketDescriptor,(sockaddr*)&address,&length);
    // 如果客户端socket无效，则抛出异常
    if(clientSocketDescriptor == -1){
        ThrowErrorNoException(env,"java/lang/IOException",errno);
    }else{
        LogAddress(env,obj,"Client connect from",&address);
    }
    return clientSocketDescriptor;
}

/**
 * 从客户端Socket中接收数据
 * @param env env
 * @param obj obj
 * @param socketDescriptor 客户端socket描述符
 * @param buffer 缓冲区
 * @param bufferLength 缓冲区大小
 * @return 从socket中接收的字节数
 */
static ssize_t ReceiveFromSocket(JNIEnv *env,jobject obj,
                                 int socketDescriptor,char *buffer, size_t bufferLength){
    LogMessage(env,obj,"Receiving From Socket ...");
    ssize_t recvSize = recv(socketDescriptor,buffer,bufferLength - 1,0);
    if(-1 == recvSize){     // 接收失败
        ThrowErrorNoException(env,"java/lang/IOException",errno);
    }else{
        // 最后一位设置为NULL，这样buffer就是一个完整的字符串啦
        buffer[recvSize] = NULL;
        if(recvSize > 0){   // 接收成功
            LogMessage(env,obj,"Received %d bytes:%s",recvSize,buffer);
        }else{
            LogMessage(env,obj,"Client disconnected");
        }
    }
    return recvSize;
}

/**
 * 向客户端Socket中发送数据
 * @param env env
 * @param obj obj
 * @param socketDescriptor 客户端socket描述符
 * @param buffer 缓冲区
 * @param bufferLength 缓冲区大小
 * @return 向socket中发送的字节数
 */
static ssize_t SendToSocket(JNIEnv *env,jobject obj,int socketDescriptor,const char *buffer,size_t bufferLength){
    LogMessage(env,obj,"Sending to Socket ...content:%s,length:%d",buffer,bufferLength);

    ssize_t  sendSize = send(socketDescriptor,buffer,bufferLength,0);
    LogMessage(env,obj,"Sending to Socket send size:%d",sendSize);
    __android_log_print(ANDROID_LOG_ERROR,"wangheng","send size:%d",sendSize);
    if(-1 == sendSize){     // 发送失败
        ThrowErrorNoException(env,"java/io/IOException",errno);
    }else{
        if(sendSize > 0){   // 发送成功
            LogMessage(env,obj,"Sended %d bytes:%s",sendSize,buffer);
        }else{
            LogMessage(env,obj,"Client disconnected");
        }
    }
}

void closeSocketIfNeed(int socketDescriptor){
    if(socketDescriptor > 0){
        close(socketDescriptor);
    }
}

extern "C"
JNIEXPORT void JNICALL
Java_opensource_xinye_com_test11_jni_Socket_nativeStartTcpServer(JNIEnv *env, jobject instance,
        jint port) {

    // 第一步：创建服务端Socket实例
    int socketDescriptor = NewTcpSocket(env,instance);


    if(NULL == env->ExceptionOccurred()){
        // 第二步：绑定Socket到一个端口号
        BindSocketToPort(env,instance,socketDescriptor,(unsigned short)port);
        if(NULL != env->ExceptionOccurred()){
            closeSocketIfNeed(socketDescriptor);
            return;
        }

        if(0 == port){  // 表示请求了随机端口号
            // 获取端口号
            GetSocketPort(env,instance,socketDescriptor);
            if(NULL != env->ExceptionOccurred()){
                closeSocketIfNeed(socketDescriptor);
                return;
            }
        }

        // 第三步：监听有四个等待连接backlog的是socket
        ListenSocket(env,instance,socketDescriptor,4);
        if(NULL != env->ExceptionOccurred()){
            closeSocketIfNeed(socketDescriptor);
            return;
        }

        // 第四步：接受一个socket连接
        int clientSocketDescriptor = AcceptOnSocket(env,instance,socketDescriptor);
        if(NULL != env->ExceptionOccurred()){
            closeSocketIfNeed(socketDescriptor);
            return;
        }

        // 第五步、第六步：接收和发送数据
        char buffer[MAX_BUFFER_SIZE];
        ssize_t recvSize;
        ssize_t sendSize;

        while(1){
            // 接收数据
            recvSize = ReceiveFromSocket(env,instance,clientSocketDescriptor,buffer,MAX_BUFFER_SIZE);
            if(0 == recvSize || NULL != env->ExceptionOccurred()){  // 若Socket断开或者发生异常
                break;
            }
            // 发送数据
            sendSize = SendToSocket(env,instance,clientSocketDescriptor,buffer,(size_t)recvSize);
            if(0 == sendSize || NULL != env->ExceptionOccurred()){  // 若Socket断开或者发生异常
                break;
            }
        }
        closeSocketIfNeed(socketDescriptor);
    }else{
        closeSocketIfNeed(socketDescriptor);
    }

}

/**
 * 连接给定的IP地址和端口号
 * @param env env
 * @param obj obj
 * @param socketDescription socket描述符
 * @param ip IP地址
 * @param port 端口号
 */
void ConnectToAddress(JNIEnv* env,jobject obj,int socketDescription,const char* ip,unsigned short port){
    LogMessage(env,obj,"Connect to %s:%d",ip,port);

    struct sockaddr_in address;
    memset(&address,0, sizeof(address));

    address.sin_family = PF_INET;

    // 将IP字符串转换为网络地址
    if(0 == inet_aton(ip,&(address.sin_addr))){
        ThrowErrorNoException(env,"java/io/IOException",errno);
    }else{
        // 将端口号转换为网络字节顺序
        address.sin_port = htons(port);
        if(-1 == connect(socketDescription,((const sockaddr*)&address), sizeof(address))){
            // 连接失败
            ThrowErrorNoException(env,"java/io/IOException",errno);
        }else{
            LogMessage(env,obj,"Connect Successful~");
        }
    }
}


extern "C"
JNIEXPORT void JNICALL
Java_opensource_xinye_com_test11_jni_Socket_nativeStartClient(JNIEnv *env, jobject instance,
                                                                 jstring ip_, jint port,
                                                                 jstring message_) {
    // 1. 创建客户端Socket
    int clientSocket = NewTcpSocket(env,instance);

    if(NULL == env->ExceptionOccurred()){
        // 2. 连接IP地址和端口号
        const char *ip = env->GetStringUTFChars(ip_, 0);
        ConnectToAddress(env, instance, clientSocket, ip, static_cast<unsigned short>(port));
        env->ReleaseStringUTFChars(ip_, ip);
        if(NULL != env->ExceptionOccurred()){
            closeSocketIfNeed(clientSocket);
            return;
        }

        // 3.发送消息给服务器Socket
        jsize messageSize = env->GetStringLength(message_);
        const char *message = env->GetStringUTFChars(message_, 0);
        SendToSocket(env, instance, clientSocket, message, static_cast<size_t>(messageSize));
        env->ReleaseStringUTFChars(message_, message);
        if(NULL != env->ExceptionOccurred()){
            closeSocketIfNeed(clientSocket);
            return;
        }

        // 4.接收服务端返回
        char buffer[MAX_BUFFER_SIZE];
        ReceiveFromSocket(env,instance,clientSocket,buffer, MAX_BUFFER_SIZE);
    }else{
        closeSocketIfNeed(clientSocket);
    }
}

/************************************* UDP部分 **************************************/

/**
 * 创建UDP Socket
 * @param env env
 * @param obj obj
 * @return 创建成功返回socket 标识符，否则返回-1
 */
static int NewUDPSocket(JNIEnv *env,jobject obj){
    LogMessage(env,obj,"New Udp Socket");
    int socketDescription = socket(PF_INET,SOCK_DGRAM,0);
    if(-1 == socketDescription){
        ThrowErrorNoException(env,"java/io/IOException",errno);
    }
    return socketDescription;
}

/**
 * 从Socket中接收数据
 * @param env env
 * @param obj obj
 * @param socketDescription socket标识符
 * @param address 地址
 * @param buffer 缓冲区
 * @param bufferSize 缓冲区大小
 * @return 接受的数据大小
 */
static ssize_t ReceiveDatagramFromSocket(JNIEnv *env,jobject obj,
                                         int socketDescription,
                                         struct sockaddr_in* address,
                                         char* buffer,
                                         size_t bufferSize){
    LogMessage(env,obj,"Receiving from socket....%d",socketDescription);
    socklen_t length = sizeof(struct sockaddr_in);

    ssize_t recvSize = recvfrom(socketDescription,buffer,bufferSize,0,(struct sockaddr*)address,&length);

    if(-1 == recvSize){
        ThrowErrorNoException(env,"java/io/IOException",errno);
    }else{
        // 记录地址
        LogAddress(env,obj,"Receive From ",address);
        // 以NULL结束buffer，以使其是一个字符串
        buffer[recvSize] = NULL;

        if(recvSize > 0){
            LogMessage(env,obj,"Receive %d bytes content is %s",recvSize,buffer);
        }
    }

    return recvSize;
}

/**
 * 发送数据到指定的Socket
 * @param env env
 * @param obj obj
 * @param socketDescription socket标识符
 * @param address 目标地址
 * @param buffer 发送的内容
 * @param bufferSize 发送内容的大小
 * @return 发送的字节数
 */
static ssize_t SendDatagramToSocket(JNIEnv *env,jobject obj,
                                    int socketDescription,
                                    const struct sockaddr_in *address,
                                    const char* buffer,
                                    size_t bufferSize){

    LogAddress(env,obj,"Sending to ",address);
    ssize_t sendSize = sendto(socketDescription,buffer,bufferSize,0,(const sockaddr*) address,
                              sizeof(struct sockaddr_in));
    if(-1 == sendSize){
        ThrowErrorNoException(env,"java/io/IOException",errno);
    }else{
        if(sendSize > 0){
            LogMessage(env,obj,"Send %d bytes content is %s",sendSize,buffer);
        }
    }
    return sendSize;
}

extern "C"
JNIEXPORT void JNICALL
Java_opensource_xinye_com_test11_jni_Socket_nativeStartUdpServer(JNIEnv *env, jobject instance,
                                                                 jint port) {
    // 1.构造一个Socket
    int socket = NewUDPSocket(env,instance);
    if(NULL == env->ExceptionOccurred()){
        // 2.绑定Socket到一个端口号
        BindSocketToPort(env, instance, socket, static_cast<unsigned short>(port));
        if(NULL != env->ExceptionOccurred()){
            closeSocketIfNeed(socket);
            return;
        }
        if(port == 0){
            GetSocketPort(env,instance,socket);
            if(NULL != env->ExceptionOccurred()){
                closeSocketIfNeed(socket);
                return;
            }
        }

        // 3.从客户端接收信息
        struct sockaddr_in address;
        memset(&socket,0, sizeof(address));

        char  buffer[MAX_BUFFER_SIZE];
        ssize_t recvSize;
        ssize_t sendSize;

        recvSize = ReceiveDatagramFromSocket(env,instance,socket,&address,buffer,MAX_BUFFER_SIZE);
        if(0 == recvSize || NULL != env->ExceptionOccurred()){
            closeSocketIfNeed(socket);
            return;
        }

        sendSize = SendDatagramToSocket(env, instance, socket, &address, buffer,
                                        static_cast<size_t>(recvSize));
        closeSocketIfNeed(socket);
    }else{
        closeSocketIfNeed(socket);
    }
}

extern "C"
JNIEXPORT void JNICALL
Java_opensource_xinye_com_test11_jni_Socket_nativeStartUdpClient(JNIEnv *env, jobject instance,
                                                                 jstring ip_, jint port,
                                                                 jstring message_) {
    // 1.创建客户端So课堂
    int clientSocket = NewUDPSocket(env,instance);
    if(NULL == env->ExceptionOccurred()){
        struct sockaddr_in address;
        memset(&address,0, sizeof(address));

        // 以C字符串的形式获取IP地址
        const char *ip = env->GetStringUTFChars(ip_, 0);
        if(NULL == ip){
            closeSocketIfNeed(clientSocket);
            return;
        }

        // 将IP地址字符串转换为网络地址
        int result = inet_aton(ip,&(address.sin_addr));
        // 释放IP地址
        env->ReleaseStringUTFChars(ip_, ip);

        // 如果IP地址转换为网络地址失败
        if(0 == result){
            ThrowErrorNoException(env,"java/io/IOException",errno);
            closeSocketIfNeed(clientSocket);
            return;
        }

        // 将端口转换为网络字节顺序
        address.sin_port = htons(port);

        // 以C字符串的方式获取消息
        const char *message = env->GetStringUTFChars(message_, 0);
        if(NULL == message){
            closeSocketIfNeed(clientSocket);
            return;
        }
        // 获取消息大小
        jsize messageSize = env->GetStringUTFLength(message_);

        // 2.发送消息给Socket
        SendDatagramToSocket(env,instance,clientSocket,&address,message,messageSize);

        // 释放字符串
        env->ReleaseStringUTFChars(message_, message);

        // 如果发送未成功
        if(NULL != env->ExceptionOccurred()){
            closeSocketIfNeed(clientSocket);
            return;
        }

        // 3.接收消息
        char buffer[MAX_BUFFER_SIZE];
        memset(&address,0, sizeof(address));
        ReceiveDatagramFromSocket(env,instance,clientSocket,&address,buffer,MAX_BUFFER_SIZE);


    }else{
        closeSocketIfNeed(clientSocket);
    }
}

/**************************** LocalSocket ***************************/

/**
 * 创建一个原生的Unix Socket
 *
 * @param env env
 * @param obj obj
 * @return socket标识符
 */
static int NewLocalSocket(JNIEnv* env,jobject obj){
    LogMessage(env,obj,"Constructing a new Local Unix Socket...");
    int localSocket = socket(PF_LOCAL,SOCK_STREAM,0);
    if(-1 == localSocket){
        ThrowErrorNoException(env,"java/io/IOException",errno);
    }
    return localSocket;
}

/**
 * 绑定Socket名
 * @param env env
 * @param obj obj
 * @param socketDescription socket标识符
 * @param name socket名
 */
static void BindLocalSocketToName(JNIEnv *env,jobject obj,int socketDescription,const char* name){
    struct sockaddr_un address;
    // 名字长度
    const size_t nameLength = strlen(name);
    // 路径长度初始化与名称长度相等
    size_t pathLength = nameLength;
    // 若名字不是以'/'开头的话，则表示它在抽象命名空间中
    bool abstractNamespace = ('/' != name[0]);
    // 抽象命名空间要求目录的子一个字节是0字节，更新路径长度包括0字节
    if(abstractNamespace){
        pathLength++;
    }

    // 检查路径长度
    if(pathLength > sizeof(address.sun_path)){
        ThrowException(env,"java/io/IOException","Name is too big.");
    }else{
        // 清除地址字节
        memset(&address,0, sizeof(address));
        address.sun_family = PF_LOCAL;

        // socket路径
        char* sunPath = address.sun_path;

        // 第一个字节必须是0以使用抽象明明空间
        if(abstractNamespace){
            *sunPath++ = NULL;
        }

        // 追加本地名字
        strcpy(sunPath,name);

        // 地址长度
        socklen_t addressLength = (offsetof(struct sockaddr_un,sun_path)) + pathLength;

        // 如果socket名已经绑定，取消连接
        unlink(address.sun_path);

        // 绑定Socket
        LogMessage(env,obj,"Binding to local name %s%s",(abstractNamespace) ? "(null)" : "",name);

        if(-1 == bind(socketDescription,(struct sockaddr*)&address,addressLength)){
            ThrowErrorNoException(env,"java/io/IOException",errno);
        }
    }
}

/**
 * 阻塞并等待给定的socket上即将到来的客户端连接
 * @param env env
 * @param obj obj
 * @param socketDescription socket标识符
 * @return client socket
 */
static int AcceptOnLocalSocket(JNIEnv *env,jobject obj,int socketDescription){
    LogMessage(env,obj,"Waiting for a client connection...");
    int clientSocket = accept(socketDescription,NULL,NULL);
    if(-1 == clientSocket){
        ThrowErrorNoException(env,"java/io/IOException",errno);
    }
    return clientSocket;
}

extern "C"
JNIEXPORT void JNICALL
Java_opensource_xinye_com_test11_jni_Socket_nativeStartLocalServer(JNIEnv *env, jobject instance,
                                                                   jstring socketName_) {

    // 1.构造一个本地Unix Socket
    int serverSocket = NewLocalSocket(env,instance);
    if(NULL == env->ExceptionOccurred()){
        // 以C字符串形式获取名称
        const char *socketName = env->GetStringUTFChars(socketName_, 0);
        if(NULL == socketName){
            closeSocketIfNeed(serverSocket);
            return;
        }

        // 2.绑定Socket到摸一个端口号
        BindLocalSocketToName(env,instance,serverSocket,socketName);
        // 释放字符串
        env->ReleaseStringUTFChars(socketName_, socketName);

        // 绑定失败
        if(NULL != env->ExceptionOccurred()){
            closeSocketIfNeed(serverSocket);
            return;
        }

        // 3.监听有四个挂起连接的带backlog的socket
        ListenSocket(env,instance,serverSocket,4);
        if(NULL != env->ExceptionOccurred()){
            closeSocketIfNeed(serverSocket);
            return;
        }

        // 4.接受socket的一个客户连接
        int clientSocket = AcceptOnLocalSocket(env,instance,serverSocket);
        if(NULL != env->ExceptionOccurred()){
            closeSocketIfNeed(serverSocket);
            return;
        }

        // 5.接受并发送数据
        char buffer[MAX_BUFFER_SIZE];
        ssize_t recvSize;
        ssize_t sendSize;

        while(1){
            // 从Socket中接收
            recvSize = ReceiveFromSocket(env,instance,clientSocket,buffer,MAX_BUFFER_SIZE);
            if(0 == recvSize || NULL != env->ExceptionOccurred()){
                break;
            }
            // 发送给客户端socket
            sendSize = SendToSocket(env,instance,clientSocket,buffer,(size_t)recvSize);
            if(0 == sendSize || NULL != env->ExceptionOccurred()){
                break;
            }
        }
    }else{
        closeSocketIfNeed(serverSocket);
    }
    closeSocketIfNeed(serverSocket);

}